<template>
  <ElementDialog
    ref="dialog"
    custom-class="tree-search-picker footer-align-right normalize"
    embed
    :visible="visible"
    :title="title"
    show-full-screen
    height="50vh"
    width="800px">
    <div class="el-transfer flex-column" style="height: 100%">
      <div class="el-transfer-panel flex-column flex-item">
        <!-- 头部 -->
        <div class="el-transfer-panel__header flex-row">
          <p class="el-transfer-panel__header__column" style="width: 360px">
            {{ keyWord ? (querying ? '搜索中...' : '搜索结果') : '我的组织' }}
          </p>
          <p class="el-transfer-panel__header__column flex-item">
            已选
            <span style="color: orangered">{{ selectedResult && selectedResult.length }}</span>
            位人员
          </p>
        </div>
        <!-- 内容 -->
        <div class="el-transfer-panel__body flex-item flex-row" style="width: 100%; overflow: hidden">
          <!-- 左边 -->
          <div class="panel-column flex-not-shrink" style="width: 360px">
            <template v-if="searchable">
              <ElInput
                v-model="keyWord"
                class="el-transfer-panel__filter flex-not-shrink"
                clearable
                placeholder="请输入姓名搜索"
                prefix-icon="el-icon-search" />
              <ul v-show="keyWord && lazy" class="flex-item" style="overflow: auto" @click.stop>
                <li
                  v-for="item in searchResult"
                  :key="item[nodeKey]"
                  class="selected-user-item flex-row"
                  style="cursor: pointer"
                  @click.stop="add(item)">
                  <i class="el-icon-user"></i>
                  <p class="selected-user-name flex-item">{{ getLabel(item) }}</p>
                </li>
                <li
                  v-show="!querying && keyWord && searchResult && searchResult.length === 0"
                  class="el-transfer-panel__empty">
                  无匹配数据
                </li>
              </ul>
            </template>
            <ElTree
              v-show="(lazy && !keyWord) || !lazy"
              :key="treeKey"
              ref="tree"
              class="flex-item"
              style="overflow: auto"
              :default-checked-keys="checkedKeys"
              :default-expanded-keys="expandedKeys"
              :load="loadData"
              :props="defaultProps"
              check-on-click-node
              check-strictly
              highlight-current
              :lazy="lazy"
              :filter-node-method="filterNode"
              :data="data"
              :node-key="nodeKey"
              :show-checkbox="false"
              @check="check">
              <template #default="{ node, data: sData }">
                <span :title="node.label" class="flex-item flex-row" style="padding-right: 10px; overflow: hidden">
                  <template v-if="['DW', 'BM'].includes(sData.type)">
                    <span class="flex-not-shrink" style="margin-right: 4px">
                      <i class="el-icon-office-building"></i>
                    </span>
                  </template>
                  <template v-else>
                    <span class="flex-not-shrink" style="margin-right: 4px">
                      <ElCheckbox
                        v-if="multiple"
                        class="user-disabled-checkbox"
                        style="margin-right: 4px; pointer-events: none"
                        :disabled="node.disabled"
                        :value="node.checked" />
                      <ElRadio v-else style="margin-right: 0" :label="true" :value="node.checked"
                        ><span></span
                      ></ElRadio>
                      <i class="el-icon-user"></i>
                    </span>
                  </template>
                  <span class="flex-item ellipsis-1">{{ node.label }}</span>
                  <template v-if="!lazy && multiple && ['DW', 'BM'].includes(sData.type) && sData.children.length">
                    <span class="flex-not-shrink actions" style="padding: 0 10px">
                      <i
                        class="el-icon-circle-plus-outline"
                        title="添加"
                        style="margin-right: 10px; color: #1ddc1d"
                        @click.stop="addAll(sData, node)"></i>
                      <i
                        class="el-icon-remove-outline"
                        title="移除"
                        style="color: #ff1e1e"
                        @click.stop="removeAll(sData, node)"></i>
                    </span>
                  </template>
                </span>
              </template>
            </ElTree>
          </div>
          <!-- 右边 -->
          <div class="panel-column flex-item" style="width: 0; overflow: hidden">
            <ul v-show="selectedResult && selectedResult.length" class="flex-item" style="overflow: auto">
              <li
                v-for="item in selectedResult"
                :key="item[nodeKey]"
                class="selected-user-item flex-row"
                style="align-items: flex-start">
                <i class="el-icon-user-solid" style="margin-top: 4px"></i>
                <p class="selected-user-name flex-item">{{ getLabel(item) }}</p>
                <i class="el-icon-circle-close selected-user-remove" style="margin-top: 3px" @click="remove(item)"></i>
              </li>
            </ul>
            <EmptyView
              v-show="selectedResult && selectedResult.length === 0"
              font-size="16px"
              empty-tips="请选择人员" />
            <slot></slot>
          </div>
        </div>
      </div>
    </div>
    <template #footer>
      <ElButton size="mini" type="primary" @click="onConfirm">确定</ElButton>
      <ElButton size="mini" @click="onCancel">取消</ElButton>
    </template>
  </ElementDialog>
</template>

<script>
  import { isFunction, isString, treeToArray } from '@web-utils/core'
  import { cloneDeep } from '@web-utils/integrations/lodash'

  export default {
    name: 'TreeSearchPicker',
    props: {
      visible: Boolean,
      multiple: Boolean,
      destroyOnClose: Boolean,
      treeApi: Function,
      searchApi: Function,
      data: {
        type: Array
      },
      lazy: {
        type: Boolean,
        default: false
      },
      nodeKey: {
        type: String,
        default: 'id'
      },
      searchable: {
        type: Boolean,
        default: true
      },
      value: {
        type: Array,
        default: () => []
      },
      beforeConfirm: Function,
      title: {
        type: String,
        default: '选择人员'
      },
      disabledKeys: {
        type: Array,
        default: () => []
      },
      defaultExpandedKeys: {
        type: Array,
        default: () => []
      },
      defaultProps: Object
    },
    data() {
      return {
        treeKey: 1,
        keyWord: '',
        querying: false,
        expandedKeys: [],
        selectedResult: [],
        searchResult: []
      }
    },
    computed: {
      checkedKeys() {
        return this.value.map(value => value[this.nodeKey])
      }
    },
    watch: {
      defaultExpandedKeys() {
        this.expandedKeys = [...(this.defaultExpandedKeys || [])]
      },
      visible(newVal) {
        if (this.destroyOnClose && !newVal) {
          this.treeKey++
          this.expandedKeys = []
        }
        if (newVal) {
          this.setSelectedResult()
        } else {
          this.selectedResult = []
        }
      },
      selectedResult: 'setCheckedKeys',
      value: {
        immediate: true,
        handler: 'setSelectedResult'
      },
      async keyWord(newVal) {
        if (this.lazy) {
          if (!this.querying) {
            this.searchResult = []
            this.querying = true
            try {
              const res = await this.searchApi(newVal)
              this.searchResult = res.data.records || res
            } catch (e) {}
            this.querying = false
          }
        } else {
          this.$refs.tree.filter(newVal)
        }
      }
    },
    methods: {
      filterNode(value, data) {
        if (!value) return true
        return data.name.indexOf(value) !== -1
      },
      async setCheckedKeys() {
        await this.$nextTick()
        this.$refs.tree?.setCheckedKeys(this.selectedResult.map(value => value[this.nodeKey]))
      },
      getLabel(row) {
        if (isString(this.defaultProps.label)) {
          return row[this.defaultProps.label]
        } else if (isFunction(this.defaultProps.label)) {
          return this.defaultProps.label(row, true)
        } else {
          return row.label || row.name
        }
      },
      async loadData(node, resolve) {
        if (this.treeApi) {
          try {
            const res = await this.treeApi(node.level === 0 ? -1 : node.data[this.nodeKey], node)
            const data = res.data || res
            resolve(
              data.map(value => {
                value.disabled = value.disabled || this.disabledKeys.includes(value[this.nodeKey])
                return value
              })
            )
            if (node.level === 0) {
              this.expandedKeys = [data[0][this.nodeKey]]
            }
            return
          } catch (e) {}
        }
        resolve([])
      },
      check(row, checks) {
        const checked = checks.checkedKeys.includes(row[this.nodeKey])
        if (this.multiple) {
          let rows = [row]
          if (row.type !== 'user') {
            rows = treeToArray(row).filter(value => value.type === 'user')
          }
          rows.forEach(user => {
            const index = this.selectedResult.findIndex(value => value[this.nodeKey] === user[this.nodeKey])
            if (checked && index < 0) {
              this.selectedResult.push(user)
            } else {
              if (!checked && index > -1) {
                this.selectedResult.splice(index, 1)
              }
            }
          })
        } else {
          if (checked) {
            this.selectedResult = [row]
          } else {
            this.selectedResult = []
          }
        }
      },
      addAll(row) {
        const rows = treeToArray(row)
          .filter(value => value.type === 'user')
          .filter(value => !this.disabledKeys.includes(value[this.nodeKey]) && this.filterNode(this.keyWord, value))
        rows.forEach(user => {
          const index = this.selectedResult.findIndex(value => value[this.nodeKey] === user[this.nodeKey])
          if (index < 0) {
            this.selectedResult.push(user)
          }
        })
      },
      removeAll(row) {
        const rows = treeToArray(row)
          .filter(value => value.type === 'user')
          .filter(value => !this.disabledKeys.includes(value[this.nodeKey]) && this.filterNode(this.keyWord, value))
        rows.forEach(user => {
          const index = this.selectedResult.findIndex(value => value[this.nodeKey] === user[this.nodeKey])

          if (index > -1) {
            this.selectedResult.splice(index, 1)
          }
        })
      },
      add(row) {
        if (this.multiple) {
          if (this.selectedResult.findIndex(value => value[this.nodeKey] === row[this.nodeKey]) < 0) {
            this.selectedResult.push(row)
          }
        } else {
          this.selectedResult = [row]
        }
      },
      remove(row) {
        this.selectedResult = this.selectedResult.filter(value => value[this.nodeKey] !== row[this.nodeKey])
      },
      setSelectedResult() {
        const users = cloneDeep(this.value)
        users.forEach(value => {
          if (!value.key) {
            value.key = `user_${value.id}`
          }
        })
        this.selectedResult = users
      },
      close() {
        this.$refs.dialog.hide()
      },
      onCancel() {
        this.$emit('cancel')
        this.close()
      },
      onConfirm() {
        const rows = []
        this.selectedResult.forEach(value => rows.push(cloneDeep(value)))
        if (this.beforeConfirm && this.beforeConfirm(rows)) {
          this.emitInput(rows)
        } else {
          this.emitInput(rows)
        }
      },
      emitInput(rows) {
        this.$emit('input', rows)
        this.$emit('confirm', rows)
        this.close()
      }
    }
  }
</script>

<style lang="scss">
  .tree-search-picker {
    .el-dialog__body {
      padding: 0;
    }

    .el-transfer {
      align-items: center;
      justify-content: center;
    }

    .el-transfer-panel__header {
      padding-left: 0;
    }

    .el-transfer-panel__header__column {
      padding-left: 10px;

      &:last-child {
        border-left: 1px solid #ebeef5;
      }
    }

    .el-transfer-panel__filter {
      margin: 10px;
    }

    .el-transfer-panel {
      width: 100%;
    }

    .el-transfer-panel__body {
      .el-checkbox.is-disabled {
        display: none;

        &.user-disabled-checkbox {
          display: inline-block;
          margin-right: 8px;
        }
      }
    }

    .panel-column {
      @include flex-column;
      @include flex-not-shrink;

      &:last-child {
        border-left: 1px solid #ebeef5;
      }
    }

    .el-transfer__buttons {
      padding: 0 20px;
    }

    .selected-user-item {
      align-items: center;
      padding: 10px;
      text-align: left;

      .selected-user-remove {
        display: none;
      }

      &:hover {
        color: var(--color-primary);
        background-color: #f5f7fa;

        .selected-user-remove {
          display: block;
          color: grey;
          font-size: 1.2em;

          &:hover {
            color: red;
            cursor: pointer;
          }
        }
      }

      .selected-user-name {
        padding-left: 4px;
      }
    }

    .el-transfer-panel__filter {
      margin: 9px;
      overflow: hidden;

      .el-input__suffix {
        right: -20px;
      }
    }

    .el-tree-node__content {
      .actions {
        display: none;
      }

      &:hover {
        .actions {
          display: inline;
        }
      }
    }
  }
</style>
